<?php
	
	namespace Aspic;

	/**
	 * Basic node tree with a name and childs nodes
	 */
	class Node {

		protected $_name;
		protected $_childs;
		protected $_parent;

		public function __construct($name, array $childs = array()) {
			$this->_name = $name;
			$this->_parent = null;
			$this->_childs = array();
			
			foreach($childs as $child) {
				$this->addChild($child);
			}
		}
		
		public function getName() {
			return $this->_name;
		}
		
		public function addChild(Node $child) {
			$this->_childs[] = $child;
			$child->_parent = $this;
			
			return $this;
		}
		
		public function removeChild(Node $child) {
			$child->_parent = null;
			
			$this->_childs = UArray::removeFromValue($this->_childs, $child, true);
		}
		
		/**
		 * Check if node has a parent
		 * @return boolean
		 */
		public function hasParent() {
			return is_a($this->_parent, get_called_class());
		}
		
		/**
		 * Return the parent of the node
		 * @return Node
		 * @throws Exception
		 */
		public function getParent() {
			if ($this->hasParent()) {
				return $this->_parent;
			}
			else {
				throw new Exception('Node has no parent');
			}
		}
		
		public function getChilds() {
			return $this->_childs;
		}
		
		public function hasChilds() {
			return count($this->getChilds()) > 0;
		}
		
		public function findChild($childName) {
			foreach($this->getChilds() as $child) {
				if ($child->getName() == $childName) {
					return $child;
				}
			}
			
			return null;
		}
		
		/**
		 * Return the position of the Node in its parent childs
		 * @return integer
		 * @throws Exception
		 */
		public function getPosition() {
			foreach($this->getParent()->getChilds() as $index => $parentChild) {
				if($parentChild == $this) {
					return $index;
				}
			}
			
			throw new Exception('Parent does not contains child');
		}
		
		/**
		 * Return the next node in the tree
		 * @param bool $canGoInChilds If true the next node could be in current node childs
		 * @param bool $canGoUp If true the next node could be in current node parent's next child
		 * @return null|Node
		 */
		public function next($canGoInChilds = true, $canGoUp = true) {
			
			if ($this->hasParent() AND  $this->getParent()->hasChilds()) {
				$position = $this->getPosition();
				$parentChilds = $this->getParent()->getChilds();
			}
			
			if ($canGoInChilds AND $this->hasChilds()) {
				$childs = $this->getChilds();
				
				return $childs[0];
			}
			elseif ($this->hasParent() AND isset($parentChilds[$position + 1])) {
				return $parentChilds[$position + 1];
			}
			elseif ($canGoUp AND $this->hasParent()) {
				return $this->getParent()->next(false);
			}
			else {
				return null;
			}
		}
		
		/**
		 * Return the node's parent next child
		 * @return Node
		 */
		public function nextParent() {
			$parentNext = $this->getParent()->next(false);
			
			return $parentNext;
		}
		
		/**
		 * Same as next() but reversed
		 * @param type $canGoInPreviousChilds
		 * @param type $canGoUp
		 * @return null|Node
		 */
		public function prev($canGoInPreviousChilds = true, $canGoUp = true) {
			$position = $this->getPosition();
			
			$parentChilds = $this->getParent()->getChilds();
			
			if (isset($parentChilds[$position - 1])) {
				$previous = $parentChilds[$position - 1];
				
				if ($canGoInPreviousChilds AND $previous->hasChilds()) {
					$previousChilds = $previous->getChilds();
					
					return $previousChilds[count($previousChilds) - 1];
				}
				else {
					return $previous;
				}
			}
			elseif ($canGoUp AND $this->hasParent()) {
				return $this->getParent();
			}
			else {
				return null;
			}
		}

	}
	
?>